#include "pch.h"
#include "SMS/Debugger/SmsVdpTools.h"
#include "SMS/SmsTypes.h"
#include "SMS/SmsConsole.h"
#include "SMS/SmsVdp.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/MemoryDumper.h"
#include "Shared/SettingTypes.h"

SmsVdpTools::SmsVdpTools(Debugger* debugger, Emulator *emu, SmsConsole* console) : PpuTools(debugger, emu)
{
	_console = console;
}

void SmsVdpTools::SetMemoryAccessData(uint16_t scanline, SmsVdpMemAccess* data, uint16_t scanlineCount)
{
	_scanlineCount = scanlineCount;
	memcpy(_memAccess + scanline * 342, data, 342);
}

FrameInfo SmsVdpTools::GetTilemapSize(GetTilemapOptions options, BaseState& baseState)
{
	SmsVdpState& state = (SmsVdpState&)baseState;

	if(options.Layer == 1) {
		//Mem accesses
		return { 342, _scanlineCount };
	}

	bool isTextMode = !state.UseMode4 && state.M1_Use224LineMode;
	return { isTextMode ? 240u : 256u, state.NametableHeight };
}

DebugTilemapInfo SmsVdpTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer)
{
	SmsVdpState& state = (SmsVdpState&)baseState;

	if(options.Layer == 1) {
		//Mem accesses
		DebugTilemapInfo result = {};
		result.TileHeight = 1;
		result.TileWidth = 1;
		result.RowCount = _scanlineCount;
		result.ColumnCount = 342;
		result.Bpp = 8;
		result.Format = TileFormat::DirectColor;
		for(int i = 0, len = (int)(result.RowCount * result.ColumnCount); i < len; i+=2) {
			switch(_memAccess[i]) {
				case SmsVdpMemAccess::None: outBuffer[i] = 0xFF606060; break;
				case SmsVdpMemAccess::BgLoadTable: outBuffer[i] = 0xFF00FF00; break;
				case SmsVdpMemAccess::BgLoadTile: outBuffer[i] = 0xFF0000FF; break;
				case SmsVdpMemAccess::SpriteEval: outBuffer[i] = 0xFFFF0000; break;
				case SmsVdpMemAccess::SpriteLoadTable: outBuffer[i] = 0xFF00E080; break;
				case SmsVdpMemAccess::SpriteLoadTile: outBuffer[i] = 0xFF0080E0; break;
				case SmsVdpMemAccess::CpuSlot: outBuffer[i] = 0xFFA0A0A0; break;
			}

			outBuffer[i + 1] = outBuffer[i];
		}
		return result;
	}

	bool isGameGear = _console->GetModel() == SmsModel::GameGear;
	bool isTextMode = !state.UseMode4 && state.M1_Use224LineMode;

	DebugTilemapInfo result = {};
	result.Bpp = state.UseMode4 ? 4 : 1;
	result.Format = state.UseMode4 ? TileFormat::SmsBpp4 : TileFormat::SmsSgBpp1;
	result.TileWidth = isTextMode ? 6 : 8;
	result.TileHeight = 8;
	result.ColumnCount = isTextMode ? 40 : 32;
	result.RowCount = state.NametableHeight / 8;
	result.TilesetAddress = 0;
	result.ScrollWidth = isGameGear ? 160 : 256;
	result.ScrollHeight = isGameGear ? 144 : state.VisibleScanlineCount;
	
	uint8_t colorMask = 0xFF;
	if(options.DisplayMode == TilemapDisplayMode::Grayscale) {
		palette = (uint32_t*)_grayscaleColorsBpp4;
		colorMask = 0x0F;
	}

	if(state.UseMode4) {
		result.ScrollX = 256 - state.HorizontalScroll + (isGameGear ? 48 : 0);
		result.ScrollY = state.VerticalScroll + (isGameGear ? 24 : 0);
		result.TilemapAddress = state.EffectiveNametableAddress;

		for(uint8_t row = 0; row < result.RowCount; row++) {
			for(uint8_t column = 0; column < 32; column++) {
				uint16_t entryAddr = state.EffectiveNametableAddress + ((row * 32 + column) * 2);
				uint16_t ntData = vram[entryAddr] | (vram[entryAddr + 1] << 8);

				uint8_t paletteOffset = ntData & 0x800 ? 0x10 : 0;
				bool hMirror = ntData & 0x200;
				bool vMirror = ntData & 0x400;
				uint16_t tileIndex = ntData & 0x1FF;

				for(int y = 0; y < 8; y++) {
					uint8_t tileRow = vMirror ? 7 - (y & 0x07) : (y & 0x07);
					uint16_t tileAddr = tileIndex * 32 + tileRow * 4;
					for(int x = 0; x < 8; x++) {
						uint8_t tileColumn = hMirror ? 7 - (x & 0x07) : (x & 0x07);

						uint8_t color = GetTilePixelColor<TileFormat::SmsBpp4>(vram, 0x3FFF, tileAddr, tileColumn);
						uint16_t palAddr = color == 0 ? 0 : (paletteOffset + color);
						uint32_t outPos = (row * 8 + y) * 32 * 8 + column * 8 + x;
						outBuffer[outPos] = palette[palAddr & colorMask];
					}
				}
			}
		}
	} else if(state.M1_Use224LineMode) {
		//Text mode (mode 1)
		result.TilemapAddress = state.NametableAddress;
		for(uint8_t row = 0; row < result.RowCount; row++) {
			for(uint8_t column = 0; column < result.ColumnCount; column++) {
				uint16_t ntAddr = state.NametableAddress + (column + row * 40);

				uint16_t tileIndex = vram[ntAddr];
				uint16_t tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8);

				for(int y = 0; y < 8; y++) {
					for(int x = 0; x < 6; x++) {
						uint32_t outPos = (row * 8 + y) * 40 * 6 + column * 6 + x;
						uint8_t colorBit = GetTilePixelColor<TileFormat::SmsSgBpp1>(vram, 0x3FFF, tileAddr + y, x);
						outBuffer[outPos] = palette[colorBit ? state.TextColorIndex : state.BackgroundColorIndex];
					}
				}
			}
		}
	} else {
		//Graphic 1 / Graphic 2 / Multicolor
		result.TilemapAddress = state.NametableAddress;

		for(uint8_t row = 0; row < result.RowCount; row++) {
			for(uint8_t column = 0; column < result.ColumnCount; column++) {
				uint16_t ntAddr = state.NametableAddress + (column + row * 32);

				uint16_t tileIndex = vram[ntAddr];
				uint16_t tileAddr;
				if(state.M3_Use240LineMode) {
					tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8) + (row & 0x03) * 2;
				} else if(state.M2_AllowHeightChange) {
					//Move to the next 256 tiles after every 8 tile rows
					tileIndex += (row & 0x18) << 5;
					uint16_t mask = ((state.BgPatternTableAddress >> 3) | 0xFF) & 0x3FF;
					tileAddr = (state.BgPatternTableAddress & 0x2000) + ((tileIndex & mask) * 8);
				} else {
					tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8);
				}

				uint16_t colorTableAddr;
				if(state.M2_AllowHeightChange) {
					uint16_t mask = ((state.ColorTableAddress >> 3) | 0x07) & 0x3FF;
					colorTableAddr = (state.ColorTableAddress & 0x2000) | ((tileIndex & mask) << 3);
				} else {
					colorTableAddr = (state.ColorTableAddress & 0x3FC0) | ((tileIndex >> 3) & 0x1F);
				}

				for(int y = 0; y < 8; y++) {
					uint8_t color;
					if(state.M3_Use240LineMode) {
						color = vram[tileAddr + (y >= 4 ? 1 : 0)];
					} else if(state.M2_AllowHeightChange) {
						color = vram[colorTableAddr + y];
					} else {
						color = vram[colorTableAddr];
					}

					for(int x = 0; x < 8; x++) {
						uint32_t outPos = (row * 8 + y) * 32 * 8 + column * 8 + x;
						uint8_t pixelColor;
						if(state.M3_Use240LineMode) {
							pixelColor = x < 4 ? (color >> 4) : (color & 0xF);
						} else {
							uint8_t colorBit = GetTilePixelColor<TileFormat::SmsSgBpp1>(vram, 0x3FFF, tileAddr + y, x);
							pixelColor = colorBit ? (color >> 4) : (color & 0xF);
						}
						outBuffer[outPos] = palette[pixelColor];
					}
				}
			}
		}
	}

	return result;
}

DebugTilemapTileInfo SmsVdpTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState)
{
	DebugTilemapTileInfo result = {};

	FrameInfo size = GetTilemapSize(options, baseState);
	if(x >= size.Width || y >= size.Height || options.Layer == 1) {
		return result;
	}

	SmsVdpState& state = (SmsVdpState&)baseState;
	bool isTextMode = !state.UseMode4 && state.M1_Use224LineMode;

	result.Width = isTextMode ? 6 : 8;
	result.Height = 8;

	uint8_t row = y / 8;
	uint8_t column = x / result.Width;
	
	result.Row = row;
	result.Column = column;

	if(state.UseMode4) {
		uint16_t ntIndex = (row << 5) + column;
		uint16_t entryAddr = state.EffectiveNametableAddress + (ntIndex * 2);
		uint16_t ntData = vram[entryAddr] | (vram[entryAddr + 1] << 8);
	
		uint8_t paletteOffset = ntData & 0x800 ? 0x10 : 0;
		uint16_t tileIndex = ntData & 0x1FF;

		result.TileMapAddress = entryAddr;
		result.TileIndex = tileIndex;
		result.TileAddress = tileIndex * 32;
		result.PaletteIndex = paletteOffset >> 4;
		result.PaletteAddress = paletteOffset;
		result.HighPriority = (ntData & 0x1000) ? NullableBoolean::True : NullableBoolean::False;
		result.VerticalMirroring = (ntData & 0x400) ? NullableBoolean::True : NullableBoolean::False;
		result.HorizontalMirroring = (ntData & 0x200) ? NullableBoolean::True : NullableBoolean::False;
	} else if(state.M1_Use224LineMode) {
		//Text mode (mode 1)
		uint16_t ntAddr = state.NametableAddress + (column + row * 40);
		int32_t tileIndex = vram[ntAddr];
		uint16_t tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8);
		result.TileMapAddress = ntAddr;
		result.TileIndex = tileIndex;
		result.TileAddress = tileAddr;
	} else {
		//Graphic 1 / Graphic 2 / Multicolor
		uint16_t ntAddr = state.NametableAddress + (column + row * 32);

		int32_t tileIndex = vram[ntAddr];
		uint16_t tileAddr;
		if(state.M3_Use240LineMode) {
			tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8) + (row & 0x03) * 2;
			tileIndex = -1;
		} else if(state.M2_AllowHeightChange) {
			//Move to the next 256 tiles after every 8 tile rows
			tileIndex += (row & 0x18) << 5;
			uint16_t mask = ((state.BgPatternTableAddress >> 3) | 0xFF) & 0x3FF;
			tileAddr = (state.BgPatternTableAddress & 0x2000) + ((tileIndex & mask) * 8);
		} else {
			tileAddr = (state.BgPatternTableAddress & 0x3800) + (tileIndex * 8);
		}
		result.TileMapAddress = ntAddr;
		result.TileIndex = tileIndex;
		result.TileAddress = tileAddr;

		int32_t colorTableAddr;
		if(state.M3_Use240LineMode) {
			colorTableAddr = -1;
		} else if(state.M2_AllowHeightChange) {
			uint16_t mask = ((state.ColorTableAddress >> 3) | 0x07) & 0x3FF;
			colorTableAddr = (state.ColorTableAddress & 0x2000) | ((tileIndex & mask) << 3);
		} else {
			colorTableAddr = (state.ColorTableAddress & 0x3FC0) | ((tileIndex >> 3) & 0x1F);
		}

		result.PaletteAddress = colorTableAddr;
	}
	return result;
}

void SmsVdpTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, DebugSpriteInfo* sprites, uint32_t* spritePreviews, uint32_t* palette, uint32_t* outBuffer)
{
	SmsVdpState& state = (SmsVdpState&)baseState;
	
	uint32_t bgColor = GetSpriteBackgroundColor(options.Background, palette, false);

	std::fill(outBuffer, outBuffer + 256 * state.VisibleScanlineCount, bgColor);
	std::fill(outBuffer + 256 * state.VisibleScanlineCount, outBuffer + 256 * 256, GetSpriteBackgroundColor(options.Background, palette, true));

	if(_console->GetModel() == SmsModel::GameGear) {
		std::fill(outBuffer, outBuffer + 24 * 256, GetSpriteBackgroundColor(options.Background, palette, true));
		std::fill(outBuffer + 168 * 256, outBuffer + state.VisibleScanlineCount * 256, GetSpriteBackgroundColor(options.Background, palette, true));
		for(int i = 0; i < state.VisibleScanlineCount; i++) {
			std::fill(outBuffer + i * 256, outBuffer + i * 256 + 48, GetSpriteBackgroundColor(options.Background, palette, true));
			std::fill(outBuffer + i * 256 + 208, outBuffer + i * 256 + 256, GetSpriteBackgroundColor(options.Background, palette, true));
		}
	}

	int spriteCount = state.UseMode4 ? 64 : 32;
	for(int i = spriteCount - 1; i >= 0; i--) {
		DebugSpriteInfo& sprite = sprites[i];
		uint32_t* spritePreview = spritePreviews + i * _spritePreviewSize;

		int spritePosY = sprite.Y + 1;

		for(int y = 0; y < sprite.Height; y++) {
			for(int x = 0; x < sprite.Width; x++) {
				if(spritePosY + y >= 256) {
					spritePosY -= 256;
				}

				uint32_t color = spritePreview[y * sprite.Width + x];
				if(color != 0) {
					if(sprite.X + x >= 256 || sprite.Visibility == SpriteVisibility::Disabled) {
						continue;
					}

					//TODOSMS zoomed sprites support
					outBuffer[((spritePosY + y) * 256) + sprite.X + x] = color;
				} else {
					spritePreview[y * sprite.Width + x] = bgColor;
				}
			}
		}
	}
}

DebugSpritePreviewInfo SmsVdpTools::GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& baseState, BaseState& ppuToolsState)
{
	SmsVdpState& state = (SmsVdpState&)baseState;
	DebugSpritePreviewInfo info = {};
	info.Height = 256;
	info.Width = 256;
	info.SpriteCount = state.UseMode4 ? 64 : 32;
	info.CoordOffsetX = 0;
	info.CoordOffsetY = 1;

	if(_console->GetModel() == SmsModel::GameGear) {
		info.VisibleX = 48;
		info.VisibleY = 24;
		info.VisibleWidth = 160;
		info.VisibleHeight = 144;
	} else {
		info.VisibleX = 0;
		info.VisibleY = 0;
		info.VisibleWidth = 256;
		info.VisibleHeight = state.VisibleScanlineCount;
	}
	
	info.WrapBottomToTop = true;

	return info;
}

void SmsVdpTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t* spritePreview, uint16_t i, GetSpritePreviewOptions& options, SmsVdpState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
{
	uint8_t* oam = oamRam ? oamRam : (vram + (state.SpriteTableAddress & (state.UseMode4 ? 0x3F00 : 0x3FFF)));

	sprite.Bpp = state.UseMode4 ? 4 : 1;
	sprite.Format = state.UseMode4 ? TileFormat::SmsBpp4 : TileFormat::SmsSgBpp1;
	sprite.SpriteIndex = i;
	sprite.UseExtendedVram = false;
	sprite.RawY = state.UseMode4 ? oam[i] : oam[i*4];
	sprite.RawX = state.UseMode4 ? oam[0x80+i*2] : oam[i*4+1];
	sprite.Y = sprite.RawY;
	sprite.X = sprite.RawX;
	
	if(state.UseMode4) {
		if(state.ShiftSpritesLeft) {
			sprite.X -= 8;
		}
	} else {
		if(oam[i * 4 + 3] & 0x80) {
			sprite.X -= 32;
		}
	}

	sprite.TileIndex = state.UseMode4 ? oam[0x80 + i * 2 + 1] : oam[i*4+2];
	sprite.UseSecondTable = NullableBoolean::Undefined;

	sprite.Palette = state.UseMode4 ? 0 : (oam[i*4+3] & 0x0F);
	sprite.PaletteAddress = state.UseMode4 ? 0x10 : -1;
	sprite.Priority = DebugSpritePriority::Undefined;
	
	bool largeSprites = state.UseLargeSprites;
	sprite.Width = 8;
	sprite.Height = largeSprites ? 16 : 8;
	if(!state.UseMode4) {
		sprite.Width = sprite.Height;
	}

	if(!state.UseMode4 && sprite.Palette == 0) {
		sprite.Visibility = SpriteVisibility::Disabled;
	} else if(sprite.Y < state.VisibleScanlineCount || (sprite.Y > state.VisibleScanlineCount && (uint8_t)(sprite.Y + sprite.Height) >= 0)) {
		sprite.Visibility = SpriteVisibility::Visible;
	} else {
		sprite.Visibility = SpriteVisibility::Offscreen;
	}

	uint16_t tileIndex = sprite.TileIndex;
	if(largeSprites) {
		tileIndex &= state.UseMode4 ? ~0x01 : ~0x03;
	}
	uint16_t tileStart = (tileIndex * (state.UseMode4 ? 32 : 8)) | (state.SpritePatternSelector & (state.UseMode4 ? 0x2000 : ~0));
	sprite.TileAddresses[0] = tileStart;
	if(largeSprites) {
		if(state.UseMode4) {
			sprite.TileAddresses[1] = tileStart + 32;
			sprite.TileCount = 2;
		} else {
			sprite.TileAddresses[1] = tileStart + 8;
			sprite.TileAddresses[2] = tileStart + 16;
			sprite.TileAddresses[3] = tileStart + 24;
			sprite.TileCount = 4;
		}
	} else {
		sprite.TileCount = 1;
	}
	sprite.TileAddress = tileStart;

	for(int y = 0; y < sprite.Height; y++) {
		uint16_t pixelStart = tileStart + y * (state.UseMode4 ? 4 : 1);

		for(int x = 0; x < sprite.Width; x++) {
			uint8_t color;
			if(state.UseMode4) {
				color = GetTilePixelColor<TileFormat::SmsBpp4>(vram, 0x3FFF, pixelStart, x);
			} else {
				color = GetTilePixelColor<TileFormat::SmsSgBpp1>(vram, 0x3FFF, pixelStart + (x >= 8 ? 16 : 0), x & 0x07);
			}

			uint32_t outOffset = (y * sprite.Width) + x;
			if(color > 0) {
				spritePreview[outOffset] = state.UseMode4 ? palette[0x10 + color] : (sprite.Palette ? palette[sprite.Palette] : 0);
			} else {
				spritePreview[outOffset] = 0;
			}
		}
	}
}

void SmsVdpTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[], uint32_t* spritePreviews, uint32_t* screenPreview)
{
	SmsVdpState& state = (SmsVdpState&)baseState;
	int spriteCount = state.UseMode4 ? 64 : 32;

	bool disableSprites = false;
	for(int i = 0; i < spriteCount; i++) {
		outBuffer[i].Init();
		GetSpriteInfo(outBuffer[i], spritePreviews + (i * _spritePreviewSize), i, options, state, vram, oamRam, palette);

		disableSprites |= outBuffer[i].RawY == 0xD0;
		if(disableSprites) {
			outBuffer[i].Visibility = SpriteVisibility::Disabled;
		}
	}

	GetSpritePreview(options, baseState, outBuffer, spritePreviews, palette, screenPreview);
}

DebugPaletteInfo SmsVdpTools::GetPaletteInfo(GetPaletteInfoOptions options)
{
	DebugPaletteInfo info = {};

	SmsVdpState state;
	_debugger->GetPpuState(state, CpuType::Sms);

	info.ColorCount = 16;
	info.BgColorCount = 16;
	info.SpriteColorCount = 16;
	info.ColorsPerPalette = 16;
	
	if(state.UseMode4) {
		info.ColorCount = 32;
		info.SpritePaletteOffset = info.BgColorCount;
		info.HasMemType = true;
		info.PaletteMemType = MemoryType::SmsPaletteRam;
		info.RawFormat = _console->GetModel() == SmsModel::GameGear ? RawPaletteFormat::Rgb444 : RawPaletteFormat::Rgb222;

		uint8_t* paletteRam = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::SmsPaletteRam);

		if(info.RawFormat == RawPaletteFormat::Rgb222) {
			for(int i = 0; i < 32; i++) {
				info.RawPalette[i] = paletteRam[i];
				info.RgbPalette[i] = ColorUtilities::Rgb222ToArgb(paletteRam[i]);
			}
		} else {
			for(int i = 0; i < 32; i++) {
				info.RawPalette[i] = paletteRam[i * 2] | (paletteRam[i * 2 + 1] << 8);
				info.RgbPalette[i] = ColorUtilities::Rgb444ToArgb(info.RawPalette[i]);
			}
		}
	} else {
		info.ColorCount = 16;
		info.SpritePaletteOffset = 0;
		info.RawFormat = RawPaletteFormat::Indexed;
		for(int i = 0; i < 16; i++) {
			info.RawPalette[i] = _console->GetVdp()->GetSmsSgPalette()[i];
			info.RgbPalette[i] = ColorUtilities::Rgb555ToArgb(info.RawPalette[i]);
		}
	}

	return info;
}

void SmsVdpTools::SetPaletteColor(int32_t colorIndex, uint32_t color)
{
	if(_console->GetModel() == SmsModel::GameGear) {
		uint8_t r = (color >> 20) & 0x0F;
		uint8_t g = (color >> 12) & 0x0F;
		uint8_t b = (color >> 4) & 0x0F;

		uint16_t rgb444 = (b << 8) | (g << 4) | r;
		_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::SmsPaletteRam, colorIndex * 2, rgb444 & 0xFF);
		_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::SmsPaletteRam, colorIndex * 2 + 1, (rgb444 >> 8));
	} else {
		if(color < 0x3F) {
			_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::SmsPaletteRam, colorIndex, (uint8_t)color);
		}
	}
}
